import { Task } from "../../app_base/module/task/Task";


type ResourceInfo = {
    /**
     * 资源的完整原始路径
     */
    fullPath: string,
    /**
     * 资源所属的bundle下面的路径
     */
    path: string,
    /**
     * bundle资源所属bundle名称
     */
    bundle: string,
    /**
     * 是否为远程资源
     */
    isRemote: boolean,
    /**
     * 资源地址是否指向文件夹，通过最后的字符是否为"/"获得
     */
    isDir: boolean
}

/**
 * 资源管理
 */
export class ResourceManager {

    private _bundleVersionMap: Map<string, string>;
    /**
     * 预加载资源
     * @param path - `bundleName://path/to/asset`目标素材的资源地址
     * @param task - `Task` 可选参数，任务回调
     * @returns 
     * 
     * @example 
     * 
     * //预加载主包内的素材
     * preload("main://path/to/asset")
     * //预加载动态资源中的素材
     * preload("resource://path/to/asset")
     * //预加载其他包中的资源
     * preload("bundleName://path/to/asset")
     * //预加载远程服务器上的资源
     * preload("https://path/to/sound.mp3")
     * preload("https://path/to/image.png")
     * //预加载文件夹
     * preload("bundleName://path/to/dir/")
     */
    public async preload(path: string, task?: Task): Promise<string>;
    public async preload(path: string[], task?: Task): Promise<string[]>;
    public async preload(path: string | string[], task?: Task): Promise<string | string[]>;
    public async preload(path: string | string[], task?: Task): Promise<string | string[]> {
        let paths: string[] = typeof path === "string" ? [path] : path;
        let pathInfoList = paths.map((value) => {
            return this.parsePath(value);
        });
        let loadPromiseList = pathInfoList.map((info) => {
            return !info.isRemote ? this._preLoadFromBundle(info, task) : this._preLoadFromRemote(info, task);
        });
        return Promise.all(loadPromiseList).then((value) => {
            return value.length == 1 ? value[0] : value;
        });
    }

    /**
     * 预加载场景
     * @param path - `bundleName://sceneName` 目标场景的加载地址，!!不需要中间路径!!。
     * @param task - `Task` 可选参数，任务回调
     * @returns
     *  
     * @example
     * //加载主包中的场景
     * preLoadScene("main://sceneName")
     * //加载动态包中的场景
     * preLoadScene("resources://sceneName")
     * //加载其他bundle中的场景
     * preLoadScene("bundleName://sceneName")
     */
    public async preLoadScene(path: string, task?: Task): Promise<string>;
    public async preLoadScene(path: string[], task?: Task): Promise<string[]>;
    public async preLoadScene(path: string | string[], task?: Task): Promise<string | string[]>;
    public async preLoadScene(path: string | string[], task?: Task): Promise<string | string[]> {
        let paths: string[] = typeof path === "string" ? [path] : path;
        let pathInfoList = paths.map((value) => {
            let result = this.parsePath(value);
            if (result.isRemote) {
                throw new Error(`不支持从远程预加载场景${value}`);
            }
            return result;
        });
        let loadPromiseList = pathInfoList.map(async (info) => {
            let bundle: cc.AssetManager.Bundle = await this.getBundle(info.bundle);
            return new Promise<string>((resolve) => {
                bundle.preloadScene(info.path, (error) => {
                    if (error) {
                        resolve(null);
                        task && task.onError(error.name, error.message);
                    }
                    resolve(info.fullPath);
                    task && task.onComplete();
                });
            });
        });
        return Promise.all(loadPromiseList).then((value) => {
            return value.length > 0 ? (value.length == 1 ? value[0] : value) : null;
        });
    }

    private async _preLoadFromRemote(info: ResourceInfo, task?: Task): Promise<string> {
        if (!info.isRemote) {
            throw new Error(`${info.fullPath}不是远程资源地址`);
        }
        if (info.isDir) {
            throw new Error(`不支持对远程Dir的加载`);
        }
        return new Promise((resolve, reject) => {
            cc.assetManager.loadRemote(info.fullPath, (error: Error, asset: cc.Asset) => {
                if (error) {
                    resolve(null);
                    task && task.onError(error.name, error.message);
                } else {
                    resolve(info.fullPath);
                    task && task.onComplete();
                }
            })
        });
    }

    private async _preLoadFromBundle(info: ResourceInfo, task?: Task): Promise<string> {
        let bundle = await this.getBundle(info.bundle);
        return new Promise((resolve) => {
            let onProgress = (finish: number, total: number) => {
                task && task.onProgress(finish / total);
            };
            let onComplete = (error: Error) => {
                if (error) {
                    resolve(null);
                    task && task.onError(error.name, error.message);
                } else {
                    resolve(info.fullPath);
                    task && task.onComplete();
                }
            }
            if (info.path == "") {
                onComplete(null);
            }
            else if (!info.isDir) {
                bundle.preload(info.path, onProgress, onComplete);
            } else {
                bundle.preloadDir(info.path, onProgress, onComplete);
            }
        })
    }

    /**
     * 加载资源
     * @param path - `bundleName://path/to/asset`目标素材的资源地址
     * @param assetType - 可选参数，cocos资源类型（cc.Asset子类），当返回类型不是预期类型时，需要手动指定，如cc.SpriteFrame
     * @param task - `Task` 可选参数，任务回调
     * @returns 全部失败后返回null
     * 
     * @example 
     * //预加载主包内的素材
     * load("main://path/to/asset")
     * //预加载动态资源中的素材
     * load("resource://path/to/asset")
     * //预加载其他包中的资源
     * load("bundleName://path/to/image")
     * //预加载远程服务器上的资源
     * load("https://path/to/asset.mp3")
     * load("https://path/to/image.png")
     * //预加载文件夹
     * load("bundleName://path/to/dir/")
     */
    public async load<T extends cc.Asset>(path: string, assetType?: new () => T, task?: Task): Promise<T>;
    public async load<T extends cc.Asset>(path: string[], assetType?: new () => T, task?: Task): Promise<T[]>;
    public async load<T extends cc.Asset>(path: string | string[], assetType?: new () => T, task?: Task): Promise<T | T[]>;
    public async load<T extends cc.Asset>(path: string | string[], assetType?: new () => T, task?: Task): Promise<T | T[]> {
        let paths: string[] = typeof path === "string" ? [path] : path;
        let pathInfoList = paths.map((value) => {
            return this.parsePath(value);
        })
        let result: T[] = [];
        for (const info of pathInfoList) {
            if (!info.isRemote) {
                let assets = await this._loadFromBundle<T>(info, assetType, task);
                if (assets instanceof cc.Asset) {
                    result.push(assets);
                } else {
                    result.push(...assets);
                }
                continue;
            }
            let asset = await this._loadfromRemote(info, task);
            if (<new () => cc.Asset>assetType === cc.SpriteFrame) {
                asset = new cc.SpriteFrame(asset as cc.Texture2D);
            }
            result.push(<T>asset);
        }
        return typeof path === "string" ? result[0] : result;
    }

    private async _loadFromBundle<T extends cc.Asset>(info: ResourceInfo, type?: new () => T, task?: Task): Promise<T | T[]> {
        let bundle = await this.getBundle(info.bundle);
        return new Promise((resolve) => {
            let onProgress = (finish: number, total: number) => {
                task && task.onProgress(finish / total);
            };
            let onComplete = (error: Error, assets: T | T[]) => {
                if (error) {
                    resolve(null);
                    task && task.onError(error.name, error.message);
                } else {
                    resolve(assets);
                    task && task.onComplete();
                }
            }
            if (info.path == "") {
                onComplete(null, null);
            }
            else if (!info.isDir) {
                bundle.load<T>(info.path, type, onProgress, onComplete);
            } else {
                bundle.loadDir<T>(info.path, type, onProgress, onComplete);
            }
        })
    }

    private _loadfromRemote<T extends cc.Asset>(info: ResourceInfo, task?: Task): Promise<T> {
        if (!info.isRemote) {
            throw new Error(`${info.fullPath}不是远程资源地址`);
        }
        if (info.isDir) {
            throw new Error(`不支持对远程Dir的加载`);
        }
        return new Promise((resolve) => {
            cc.assetManager.loadRemote<T>(info.fullPath, (error: Error, asset: T) => {
                if (error) {
                    resolve(null);
                    task && task.onError(error.name, error.message);
                } else {
                    resolve(asset);
                    task && task.onComplete();
                }
            })
        })
    }

    /**
     * 解析资源路径成PathInfo
     * @param fullPath - 资源路径应该符合bundle://path/to/resource格式
     * @returns - `PathInfo` 资源信息
     */
    public parsePath(fullPath: string): ResourceInfo {
        let tempPath: string[] = fullPath.split("://");
        if (tempPath.length != 2) {
            throw new Error(`资源地址${fullPath}格式错误,应该符合bundle://path/to/resource格式`);//.suffix
        }
        let isDir: boolean = fullPath.charAt(fullPath.length - 1) == "/";
        let bundle: string = tempPath[0];
        let isRemote: boolean = bundle === "https" || bundle === "http";
        let suffix: string = cc.path.extname(fullPath).toLowerCase();
        let path: string = suffix == "" ? tempPath[1] : tempPath[1].replace(suffix, "");
        return { bundle, path, isRemote, isDir, fullPath };
    }

    /**
     * 获取目标bundle的资源的完整路径
     * @param bundleName - 资源所在bundle名称
     * @param subPath - 可选参数, 相对子路径
     * @returns 
     */
    public getPath(bundleName: string, subPath: string = ""): string {
        if (subPath.includes("://")) {
            return subPath;
        }
        return `${bundleName}://${subPath}`;
    }

    /**
     * 获取主包下资源的完整路径
     * @param subPath 资源子路径
     * @returns 
     */
    public getPathInMain(subPath: string): string {
        return this.getPath("main", subPath);
    }

    /**
     * 获取动态包下资源的完整路径
     * @param subPath 资源子路径
     * @returns 
     */
    public getPathInResources(subPath: string): string {
        return this.getPath("resources", subPath);
    }

    /**
     * 加载Bundle
     * @param bundName Bundle名称
     * @returns `Promise<cc.AssetManager.Bundle>`
     */
    public getBundle(bundName: string): Promise<cc.AssetManager.Bundle> {
        return new Promise((resolve) => {
            let bundle: cc.AssetManager.Bundle = cc.assetManager.getBundle(bundName);

            if (!!bundle) {
                resolve(bundle);
                return;
            }
            let version = this._getBundleVersion(bundName);
            cc.assetManager.loadBundle(bundName, version ? { version: version } : null, (error: Error, bundle: cc.AssetManager.Bundle) => {
                if (error) {
                    console.warn("LoadBundle Error:", error);
                    resolve(null);
                } else {
                    resolve(bundle);
                }
            })
        })
    }

    /**
     * 手动设置bundle的版本,可新增可以覆盖。
     * @param map 
     */
    public setBundleVersion(map: Map<string, string>): void {
        this._bundleVersionMap = this._bundleVersionMap || new Map();
        map.forEach((value, key) => {
            this._bundleVersionMap.set(value, key);
        });
    }

    private _getBundleVersion(bundleName: string): string {
        return this._bundleVersionMap && this._bundleVersionMap.get(bundleName);
    }
}
